home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2017 October / PCgo 10-2017 CD-ROM Germany.iso / nw.pak / Unnamed File 000118.txt < prev    next >
Encoding:
Text File  |  2015-07-29  |  18.3 KB  |  539 lines

  1. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. // require: event_tracker.js
  6.  
  7. // TODO(vitalyp): Inline the enums below into cr.ui definition function, remove
  8. // cr.exportPath() call and remove exportPath from exports in cr.js when this
  9. // issue will be fixed:
  10. // https://github.com/google/closure-compiler/issues/544
  11. cr.exportPath('cr.ui');
  12.  
  13. /**
  14.  * The arrow location specifies how the arrow and bubble are positioned in
  15.  * relation to the anchor node.
  16.  * @enum {string}
  17.  */
  18. cr.ui.ArrowLocation = {
  19.   // The arrow is positioned at the top and the start of the bubble. In left
  20.   // to right mode this is the top left. The entire bubble is positioned below
  21.   // the anchor node.
  22.   TOP_START: 'top-start',
  23.   // The arrow is positioned at the top and the end of the bubble. In left to
  24.   // right mode this is the top right. The entire bubble is positioned below
  25.   // the anchor node.
  26.   TOP_END: 'top-end',
  27.   // The arrow is positioned at the bottom and the start of the bubble. In
  28.   // left to right mode this is the bottom left. The entire bubble is
  29.   // positioned above the anchor node.
  30.   BOTTOM_START: 'bottom-start',
  31.   // The arrow is positioned at the bottom and the end of the bubble. In
  32.   // left to right mode this is the bottom right. The entire bubble is
  33.   // positioned above the anchor node.
  34.   BOTTOM_END: 'bottom-end'
  35. };
  36.  
  37. /**
  38.  * The bubble alignment specifies the position of the bubble in relation to
  39.  * the anchor node.
  40.  * @enum {string}
  41.  */
  42. cr.ui.BubbleAlignment = {
  43.   // The bubble is positioned just above or below the anchor node (as
  44.   // specified by the arrow location) so that the arrow points at the midpoint
  45.   // of the anchor.
  46.   ARROW_TO_MID_ANCHOR: 'arrow-to-mid-anchor',
  47.   // The bubble is positioned just above or below the anchor node (as
  48.   // specified by the arrow location) so that its reference edge lines up with
  49.   // the edge of the anchor.
  50.   BUBBLE_EDGE_TO_ANCHOR_EDGE: 'bubble-edge-anchor-edge',
  51.   // The bubble is positioned so that it is entirely within view and does not
  52.   // obstruct the anchor element, if possible. The specified arrow location is
  53.   // taken into account as the preferred alignment but may be overruled if
  54.   // there is insufficient space (see BubbleBase.reposition for the exact
  55.   // placement algorithm).
  56.   ENTIRELY_VISIBLE: 'entirely-visible'
  57. };
  58.  
  59. cr.define('cr.ui', function() {
  60.   /**
  61.    * Abstract base class that provides common functionality for implementing
  62.    * free-floating informational bubbles with a triangular arrow pointing at an
  63.    * anchor node.
  64.    * @constructor
  65.    * @extends {HTMLDivElement}
  66.    * @implements {EventListener}
  67.    */
  68.   var BubbleBase = cr.ui.define('div');
  69.  
  70.   /**
  71.    * The horizontal distance between the tip of the arrow and the reference edge
  72.    * of the bubble (as specified by the arrow location). In pixels.
  73.    * @type {number}
  74.    * @const
  75.    */
  76.   BubbleBase.ARROW_OFFSET = 30;
  77.  
  78.   /**
  79.    * Minimum horizontal spacing between edge of bubble and edge of viewport
  80.    * (when using the ENTIRELY_VISIBLE alignment). In pixels.
  81.    * @type {number}
  82.    * @const
  83.    */
  84.   BubbleBase.MIN_VIEWPORT_EDGE_MARGIN = 2;
  85.  
  86.   BubbleBase.prototype = {
  87.     // Set up the prototype chain.
  88.     __proto__: HTMLDivElement.prototype,
  89.  
  90.     /**
  91.      * @type {Node}
  92.      * @private
  93.      */
  94.     anchorNode_: null,
  95.  
  96.     /**
  97.      * Initialization function for the cr.ui framework.
  98.      */
  99.     decorate: function() {
  100.       this.className = 'bubble';
  101.       this.innerHTML =
  102.           '<div class="bubble-content"></div>' +
  103.           '<div class="bubble-shadow"></div>' +
  104.           '<div class="bubble-arrow"></div>';
  105.       this.hidden = true;
  106.       this.bubbleAlignment = cr.ui.BubbleAlignment.ENTIRELY_VISIBLE;
  107.     },
  108.  
  109.     /**
  110.      * Set the anchor node, i.e. the node that this bubble points at. Only
  111.      * available when the bubble is not being shown.
  112.      * @param {HTMLElement} node The new anchor node.
  113.      */
  114.     set anchorNode(node) {
  115.       if (!this.hidden)
  116.         return;
  117.  
  118.       this.anchorNode_ = node;
  119.     },
  120.  
  121.     /**
  122.      * Set the conent of the bubble. Only available when the bubble is not being
  123.      * shown.
  124.      * @param {HTMLElement} node The root node of the new content.
  125.      */
  126.     set content(node) {
  127.       if (!this.hidden)
  128.         return;
  129.  
  130.       var bubbleContent = this.querySelector('.bubble-content');
  131.       bubbleContent.innerHTML = '';
  132.       bubbleContent.appendChild(node);
  133.     },
  134.  
  135.     /**
  136.      * Set the arrow location. Only available when the bubble is not being
  137.      * shown.
  138.      * @param {cr.ui.ArrowLocation} location The new arrow location.
  139.      */
  140.     set arrowLocation(location) {
  141.       if (!this.hidden)
  142.         return;
  143.  
  144.       this.arrowAtRight_ = location == cr.ui.ArrowLocation.TOP_END ||
  145.                            location == cr.ui.ArrowLocation.BOTTOM_END;
  146.       if (document.documentElement.dir == 'rtl')
  147.         this.arrowAtRight_ = !this.arrowAtRight_;
  148.       this.arrowAtTop_ = location == cr.ui.ArrowLocation.TOP_START ||
  149.                          location == cr.ui.ArrowLocation.TOP_END;
  150.     },
  151.  
  152.     /**
  153.      * Set the bubble alignment. Only available when the bubble is not being
  154.      * shown.
  155.      * @param {cr.ui.BubbleAlignment} alignment The new bubble alignment.
  156.      */
  157.     set bubbleAlignment(alignment) {
  158.       if (!this.hidden)
  159.         return;
  160.  
  161.       this.bubbleAlignment_ = alignment;
  162.     },
  163.  
  164.     /**
  165.      * Update the position of the bubble. Whenever the layout may have changed,
  166.      * the bubble should either be repositioned by calling this function or
  167.      * hidden so that it does not point to a nonsensical location on the page.
  168.      */
  169.     reposition: function() {
  170.       var documentWidth = document.documentElement.clientWidth;
  171.       var documentHeight = document.documentElement.clientHeight;
  172.       var anchor = this.anchorNode_.getBoundingClientRect();
  173.       var anchorMid = (anchor.left + anchor.right) / 2;
  174.       var bubble = this.getBoundingClientRect();
  175.       var arrow = this.querySelector('.bubble-arrow').getBoundingClientRect();
  176.  
  177.       if (this.bubbleAlignment_ == cr.ui.BubbleAlignment.ENTIRELY_VISIBLE) {
  178.         // Work out horizontal placement. The bubble is initially positioned so
  179.         // that the arrow tip points toward the midpoint of the anchor and is
  180.         // BubbleBase.ARROW_OFFSET pixels from the reference edge and (as
  181.         // specified by the arrow location). If the bubble is not entirely
  182.         // within view, it is then shifted, preserving the arrow tip position.
  183.         var left = this.arrowAtRight_ ?
  184.            anchorMid + BubbleBase.ARROW_OFFSET - bubble.width :
  185.            anchorMid - BubbleBase.ARROW_OFFSET;
  186.         var max_left_pos =
  187.             documentWidth - bubble.width - BubbleBase.MIN_VIEWPORT_EDGE_MARGIN;
  188.         var min_left_pos = BubbleBase.MIN_VIEWPORT_EDGE_MARGIN;
  189.         if (document.documentElement.dir == 'rtl')
  190.           left = Math.min(Math.max(left, min_left_pos), max_left_pos);
  191.         else
  192.           left = Math.max(Math.min(left, max_left_pos), min_left_pos);
  193.         var arrowTip = Math.min(
  194.             Math.max(arrow.width / 2,
  195.                      this.arrowAtRight_ ? left + bubble.width - anchorMid :
  196.                                           anchorMid - left),
  197.             bubble.width - arrow.width / 2);
  198.  
  199.         // Work out the vertical placement, attempting to fit the bubble
  200.         // entirely into view. The following placements are considered in
  201.         // decreasing order of preference:
  202.         // * Outside the anchor, arrow tip touching the anchor (arrow at
  203.         //   top/bottom as specified by the arrow location).
  204.         // * Outside the anchor, arrow tip touching the anchor (arrow at
  205.         //   bottom/top, opposite the specified arrow location).
  206.         // * Outside the anchor, arrow tip overlapping the anchor (arrow at
  207.         //   top/bottom as specified by the arrow location).
  208.         // * Outside the anchor, arrow tip overlapping the anchor (arrow at
  209.         //   bottom/top, opposite the specified arrow location).
  210.         // * Overlapping the anchor.
  211.         var offsetTop = Math.min(documentHeight - anchor.bottom - bubble.height,
  212.                                  arrow.height / 2);
  213.         var offsetBottom = Math.min(anchor.top - bubble.height,
  214.                                     arrow.height / 2);
  215.         if (offsetTop < 0 && offsetBottom < 0) {
  216.           var top = 0;
  217.           this.updateArrowPosition_(false, false, arrowTip);
  218.         } else if (offsetTop > offsetBottom ||
  219.                    offsetTop == offsetBottom && this.arrowAtTop_) {
  220.           var top = anchor.bottom + offsetTop;
  221.           this.updateArrowPosition_(true, true, arrowTip);
  222.         } else {
  223.           var top = anchor.top - bubble.height - offsetBottom;
  224.           this.updateArrowPosition_(true, false, arrowTip);
  225.         }
  226.       } else {
  227.         if (this.bubbleAlignment_ ==
  228.             cr.ui.BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE) {
  229.           var left = this.arrowAtRight_ ? anchor.right - bubble.width :
  230.               anchor.left;
  231.         } else {
  232.           var left = this.arrowAtRight_ ?
  233.               anchorMid - this.clientWidth + BubbleBase.ARROW_OFFSET :
  234.               anchorMid - BubbleBase.ARROW_OFFSET;
  235.         }
  236.         var top = this.arrowAtTop_ ? anchor.bottom + arrow.height / 2 :
  237.             anchor.top - this.clientHeight - arrow.height / 2;
  238.         this.updateArrowPosition_(true, this.arrowAtTop_,
  239.                                   BubbleBase.ARROW_OFFSET);
  240.       }
  241.  
  242.       this.style.left = left + 'px';
  243.       this.style.top = top + 'px';
  244.     },
  245.  
  246.     /**
  247.      * Show the bubble.
  248.      */
  249.     show: function() {
  250.       if (!this.hidden)
  251.         return;
  252.  
  253.       this.attachToDOM_();
  254.       this.hidden = false;
  255.       this.reposition();
  256.  
  257.       var doc = assert(this.ownerDocument);
  258.       this.eventTracker_ = new EventTracker;
  259.       this.eventTracker_.add(doc, 'keydown', this, true);
  260.       this.eventTracker_.add(doc, 'mousedown', this, true);
  261.     },
  262.  
  263.     /**
  264.      * Hide the bubble.
  265.      */
  266.     hide: function() {
  267.       if (this.hidden)
  268.         return;
  269.  
  270.       this.eventTracker_.removeAll();
  271.       this.hidden = true;
  272.       this.parentNode.removeChild(this);
  273.     },
  274.  
  275.     /**
  276.      * Handle keyboard events, dismissing the bubble if necessary.
  277.      * @param {Event} event The event.
  278.      */
  279.     handleEvent: function(event) {
  280.       // Close the bubble when the user presses <Esc>.
  281.       if (event.type == 'keydown' && event.keyCode == 27) {
  282.         this.hide();
  283.         event.preventDefault();
  284.         event.stopPropagation();
  285.       }
  286.     },
  287.  
  288.     /**
  289.      * Attach the bubble to the document's DOM.
  290.      * @private
  291.      */
  292.     attachToDOM_: function() {
  293.       document.body.appendChild(this);
  294.     },
  295.  
  296.     /**
  297.      * Update the arrow so that it appears at the correct position.
  298.      * @param {boolean} visible Whether the arrow should be visible.
  299.      * @param {boolean} atTop Whether the arrow should be at the top of the
  300.      * bubble.
  301.      * @param {number} tipOffset The horizontal distance between the tip of the
  302.      * arrow and the reference edge of the bubble (as specified by the arrow
  303.      * location).
  304.      * @private
  305.      */
  306.     updateArrowPosition_: function(visible, atTop, tipOffset) {
  307.       var bubbleArrow = this.querySelector('.bubble-arrow');
  308.       bubbleArrow.hidden = !visible;
  309.       if (!visible)
  310.         return;
  311.  
  312.       var edgeOffset = (-bubbleArrow.clientHeight / 2) + 'px';
  313.       bubbleArrow.style.top = atTop ? edgeOffset : 'auto';
  314.       bubbleArrow.style.bottom = atTop ? 'auto' : edgeOffset;
  315.  
  316.       edgeOffset = (tipOffset - bubbleArrow.offsetWidth / 2) + 'px';
  317.       bubbleArrow.style.left = this.arrowAtRight_ ? 'auto' : edgeOffset;
  318.       bubbleArrow.style.right = this.arrowAtRight_ ? edgeOffset : 'auto';
  319.     },
  320.   };
  321.  
  322.   /**
  323.    * A bubble that remains open until the user explicitly dismisses it or clicks
  324.    * outside the bubble after it has been shown for at least the specified
  325.    * amount of time (making it less likely that the user will unintentionally
  326.    * dismiss the bubble). The bubble repositions itself on layout changes.
  327.    * @constructor
  328.    * @extends {cr.ui.BubbleBase}
  329.    */
  330.   var Bubble = cr.ui.define('div');
  331.  
  332.   Bubble.prototype = {
  333.     // Set up the prototype chain.
  334.     __proto__: BubbleBase.prototype,
  335.  
  336.     /**
  337.      * Initialization function for the cr.ui framework.
  338.      */
  339.     decorate: function() {
  340.       BubbleBase.prototype.decorate.call(this);
  341.  
  342.       var close = document.createElement('div');
  343.       close.className = 'bubble-close';
  344.       this.insertBefore(close, this.querySelector('.bubble-content'));
  345.  
  346.       this.handleCloseEvent = this.hide;
  347.       this.deactivateToDismissDelay_ = 0;
  348.       this.bubbleAlignment = cr.ui.BubbleAlignment.ARROW_TO_MID_ANCHOR;
  349.     },
  350.  
  351.     /**
  352.      * Handler for close events triggered when the close button is clicked. By
  353.      * default, set to this.hide. Only available when the bubble is not being
  354.      * shown.
  355.      * @param {function(): *} handler The new handler, a function with no
  356.      *     parameters.
  357.      */
  358.     set handleCloseEvent(handler) {
  359.       if (!this.hidden)
  360.         return;
  361.  
  362.       this.handleCloseEvent_ = handler;
  363.     },
  364.  
  365.     /**
  366.      * Set the delay before the user is allowed to click outside the bubble to
  367.      * dismiss it. Using a delay makes it less likely that the user will
  368.      * unintentionally dismiss the bubble.
  369.      * @param {number} delay The delay in milliseconds.
  370.      */
  371.     set deactivateToDismissDelay(delay) {
  372.       this.deactivateToDismissDelay_ = delay;
  373.     },
  374.  
  375.     /**
  376.      * Hide or show the close button.
  377.      * @param {boolean} isVisible True if the close button should be visible.
  378.      */
  379.     set closeButtonVisible(isVisible) {
  380.       this.querySelector('.bubble-close').hidden = !isVisible;
  381.     },
  382.  
  383.     /**
  384.      * Show the bubble.
  385.      */
  386.     show: function() {
  387.       if (!this.hidden)
  388.         return;
  389.  
  390.       BubbleBase.prototype.show.call(this);
  391.  
  392.       this.showTime_ = Date.now();
  393.       this.eventTracker_.add(window, 'resize', this.reposition.bind(this));
  394.     },
  395.  
  396.     /**
  397.      * Handle keyboard and mouse events, dismissing the bubble if necessary.
  398.      * @param {Event} event The event.
  399.      * @suppress {checkTypes}
  400.      * TODO(vitalyp): remove suppression when the extern
  401.      * Node.prototype.contains() will be fixed.
  402.      */
  403.     handleEvent: function(event) {
  404.       BubbleBase.prototype.handleEvent.call(this, event);
  405.  
  406.       if (event.type == 'mousedown') {
  407.         // Dismiss the bubble when the user clicks on the close button.
  408.         if (event.target == this.querySelector('.bubble-close')) {
  409.           this.handleCloseEvent_();
  410.         // Dismiss the bubble when the user clicks outside it after the
  411.         // specified delay has passed.
  412.         } else if (!this.contains(event.target) &&
  413.             Date.now() - this.showTime_ >= this.deactivateToDismissDelay_) {
  414.           this.hide();
  415.         }
  416.       }
  417.     },
  418.   };
  419.  
  420.   /**
  421.    * A bubble that closes automatically when the user clicks or moves the focus
  422.    * outside the bubble and its target element, scrolls the underlying document
  423.    * or resizes the window.
  424.    * @constructor
  425.    * @extends {cr.ui.BubbleBase}
  426.    */
  427.   var AutoCloseBubble = cr.ui.define('div');
  428.  
  429.   AutoCloseBubble.prototype = {
  430.     // Set up the prototype chain.
  431.     __proto__: BubbleBase.prototype,
  432.  
  433.     /**
  434.      * Initialization function for the cr.ui framework.
  435.      */
  436.     decorate: function() {
  437.       BubbleBase.prototype.decorate.call(this);
  438.       this.classList.add('auto-close-bubble');
  439.     },
  440.  
  441.     /**
  442.      * Set the DOM sibling node, i.e. the node as whose sibling the bubble
  443.      * should join the DOM to ensure that focusable elements inside the bubble
  444.      * follow the target element in the document's tab order. Only available
  445.      * when the bubble is not being shown.
  446.      * @param {HTMLElement} node The new DOM sibling node.
  447.      */
  448.     set domSibling(node) {
  449.       if (!this.hidden)
  450.         return;
  451.  
  452.       this.domSibling_ = node;
  453.     },
  454.  
  455.     /**
  456.      * Show the bubble.
  457.      */
  458.     show: function() {
  459.       if (!this.hidden)
  460.         return;
  461.  
  462.       BubbleBase.prototype.show.call(this);
  463.       this.domSibling_.showingBubble = true;
  464.  
  465.       var doc = this.ownerDocument;
  466.       this.eventTracker_.add(doc, 'mousewheel', this, true);
  467.       this.eventTracker_.add(doc, 'scroll', this, true);
  468.       this.eventTracker_.add(doc, 'elementFocused', this, true);
  469.       this.eventTracker_.add(window, 'resize', this);
  470.     },
  471.  
  472.     /**
  473.      * Hide the bubble.
  474.      */
  475.     hide: function() {
  476.       BubbleBase.prototype.hide.call(this);
  477.       this.domSibling_.showingBubble = false;
  478.     },
  479.  
  480.     /**
  481.      * Handle events, closing the bubble when the user clicks or moves the focus
  482.      * outside the bubble and its target element, scrolls the underlying
  483.      * document or resizes the window.
  484.      * @param {Event} event The event.
  485.      * @suppress {checkTypes}
  486.      * TODO(vitalyp): remove suppression when the extern
  487.      * Node.prototype.contains() will be fixed.
  488.      */
  489.     handleEvent: function(event) {
  490.       BubbleBase.prototype.handleEvent.call(this, event);
  491.  
  492.       switch (event.type) {
  493.         // Close the bubble when the user clicks outside it, except if it is a
  494.         // left-click on the bubble's target element (allowing the target to
  495.         // handle the event and close the bubble itself).
  496.         case 'mousedown':
  497.           var target = assertInstanceof(event.target, Node);
  498.           if (event.button == 0 && this.anchorNode_.contains(target))
  499.             break;
  500.         // Close the bubble when the underlying document is scrolled.
  501.         case 'mousewheel':
  502.         case 'scroll':
  503.           var target = assertInstanceof(event.target, Node);
  504.           if (this.contains(target))
  505.             break;
  506.         // Close the bubble when the window is resized.
  507.         case 'resize':
  508.           this.hide();
  509.           break;
  510.         // Close the bubble when the focus moves to an element that is not the
  511.         // bubble target and is not inside the bubble.
  512.         case 'elementFocused':
  513.           var target = assertInstanceof(event.target, Node);
  514.           if (!this.anchorNode_.contains(target) && !this.contains(target))
  515.             this.hide();
  516.           break;
  517.       }
  518.     },
  519.  
  520.     /**
  521.      * Attach the bubble to the document's DOM, making it a sibling of the
  522.      * |domSibling_| so that focusable elements inside the bubble follow the
  523.      * target element in the document's tab order.
  524.      * @private
  525.      */
  526.     attachToDOM_: function() {
  527.       var parent = this.domSibling_.parentNode;
  528.       parent.insertBefore(this, this.domSibling_.nextSibling);
  529.     },
  530.   };
  531.  
  532.  
  533.   return {
  534.     BubbleBase: BubbleBase,
  535.     Bubble: Bubble,
  536.     AutoCloseBubble: AutoCloseBubble
  537.   };
  538. });
  539.